這篇講述會比較片段一點,一些Golang常用的資料結構,使用上的小細節。
range 是Golang做interation的關鍵字,基本上與for迴圈做配合。
range for
var list []string
list = append(list, "1", "2", "3")
for _, v := range list {
log.Println(v)
}
range map
exMap := make(map[int]string)
exMap[1] = "a"
exMap[2] = "b"
exMap[3] = "c"
for _, v := range exMap {
log.Println(v)
}
range map 與range slice有個差異,對於slice,內容的元素會照順序一個一個挑出來,做了100次range slice,100次挑出內容元素的順序都是一樣。
而range map 就是亂數挑選出內容的元素了,同樣的資料做range 100次可能出來的順序都不同,所以若有講究range 內容元素出現的順序,map就是一個很不適合的設計方式。
range channel 又是另外一回事,千萬不要用range slice或者range map的概念去想range channel,對於Golang來說range channel是一個特別的組合技,有興趣細究可以參考Go by Example上面的說明。
下面是一部分worker pool 的程式碼
jobs := make(chan int, 100)
func worker(id int, jobs <-chan int, results chan<- int) {
for j := range jobs {
fmt.Println("worker", id, "started job", j)
time.Sleep(time.Second)
fmt.Println("worker", id, "finished job", j)
results <- j * 2
}
}
range 並不會因為把jobs的內容元素挑完,就離開這段程式,概念不是這樣子去想。
jobs 是一個buffer channel,channel的內容什麼時候可以挑完?或者挑的時候沒東西就作罷嗎?
不不不,要確保channel沒東西只有在channel被關閉,或者發生deadlock的時候,所以反推來想,在buffer channel 未被關閉之前,這個range channel 會一直卡在那邊,一但有資料可以從channel拿出來,則執行for迴圈內部的程式碼,然後繼續等待著從下個可以從channel拿出東西的時機。
上述的特性,就可以有很多應用,可以想像一下,上述程式碼的worker method,把他看做創建了某個工人,這個工人會一直stand by 在那邊,等待工作,一但有工作丟給他,他會立即知道,並且開始做事,乖乖的做完工作(for迴圈內的程式碼),才會再看channel能不能拿到新工作,一有新工作立即接手出來做事,持續這樣的循環。
配合goroutine的使用,如果用goroutine的方式創建這些工人,要幾個工人就有幾個工人,(要記得注意系統資源和leak問題),也因為是開goroutine,程式碼就會往下繼續執行,換句話說,你這個工頭就可以專注繼續做自己的事情。
若有需要做的工作,就將工作丟進排隊的序列,也就是上面jobs這個buffer channel就好,因為channel的特性,丟進去的工作會依序開始處理,而會分到哪個工人身上,是隨機分配,但是有正在做工作的工人就不會去拿新工作,剩下有空閒的工人就有機會拿到尚未處理的工作,就是worker pools的原理。
以上大概是一種工人先找好,確定好工作人數,再發配工作執行的概念,工人一直stand by 在那邊,你隨時要丟工作過去都可以,丟了多少也都可以,每個工人都會做事,做完了立刻去拿新的來做,直到拿不到工作,就回到stand by,而且每個人的工作量基本上很公平。這個機制運用在控管連線上非常有幫助,因為我們希望連線的數量是穩定控制的,不會突然爆多,或者沒有充分使用到資源。
worker pools的概念,與前面爆連線那篇有些相似,重複利用就能節省資源。
使用worker pools的設計,一開始建立出數個工人,工人固定stand by,有工作他們就做,換句話說後續你也就不會重複一直建新的工人出來。
是否有想過建立出工人,這也是要花系統資源的,開goroutine雖然成本便宜,但終究也是需要一定資源,再者就好比現實社會,你能夠請多少個工人,也要看你的能力所及,看你能發多少薪水,系統並不是可以無限制地開出goroutine。
而開一個goroutine,指定做某項工作,做完就消失,但後續可能又有類似的工作進來,又重新開goroutine,做完又消失,這就是一種很嚴重的浪費,如果允許的話,重複利用不是更好呢?
Golang 的FAQ開宗明義,有些設計就是為了快而誕生的,本身這部分底子好,我們除了便利地使用這些Golang準備好的東西,再加上正確的程式架構設計觀念,能夠發揮出更加的效能優勢。